package org.elzat.healthboxgatewayapp.security;

import com.alibaba.fastjson.JSON;
import org.elzat.healthboxgatewayapp.config.HealthBoxSecurityConfig;
import org.elzat.healthboxgatewayapp.config.HealthBoxSecurityConfig.ValidSecurityFilter;
import org.elzat.healthboxgatewayapp.util.ResultEntity;
import org.elzat.healthboxgatewayapp.util.ServerResponseUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

@Configuration
@EnableWebFluxSecurity
public class HealthBoxSecurity {

    private final ReactiveAuthenticationManager authenticationManager;
    private final ServerSecurityContextRepository contextRepository;
    private final HealthBoxSecurityConfig securityConfig;

    public HealthBoxSecurity(ReactiveAuthenticationManager authenticationManager,
                             ServerSecurityContextRepository contextRepository,
                             HealthBoxSecurityConfig securityConfig) {
        this.authenticationManager = authenticationManager;
        this.contextRepository = contextRepository;
        this.securityConfig = securityConfig;
    }

    @Bean
    SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        List<String> paths = securityConfig.getPermitAll();
        List<ValidSecurityFilter> filters = securityConfig.resolveFilters();
        AuthorizeExchangeSpec spec =
                http
                    .exceptionHandling()
                        .authenticationEntryPoint(entryPoint())
                        .accessDeniedHandler(deniedHandler())
                    .and()
                    .csrf().disable()
                    .formLogin()
                        .disable()
                    .httpBasic()
                        .disable()
                    .cors()
                        .disable()
                    .authenticationManager(authenticationManager)
                    .securityContextRepository(contextRepository)
                    .authorizeExchange()
                        .pathMatchers(paths.toArray(new String[0])).permitAll();
        for (ValidSecurityFilter filter : filters)
            spec
                    .pathMatchers(filter.getMethod(), filter.getPath())
                    .hasAuthority(filter.getAuthority().getAuthority());
         return spec
                .anyExchange()
                    .authenticated()
                .and().build();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    ServerAccessDeniedHandler deniedHandler() {
        return new HealthBoxAccessDeniedHandler();
    }
    @Bean
    ServerAuthenticationEntryPoint entryPoint() {
        return new HealthBoxAuthenticationEntryPoint();
    }
    private static class HealthBoxAccessDeniedHandler implements ServerAccessDeniedHandler {
        @Override
        public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
            ResultEntity<Object> result = ResultEntity
                    .forbidden()
                    .msg("拒绝访问")
                    .build();
            ServerHttpResponse response = exchange.getResponse();
            String json = JSON.toJSONString(result);
            DataBuffer buffer = ServerResponseUtil.writeJson2Buffer(response, json);
            ServerResponseUtil.setJsonHeaders(response);
            return response.writeAndFlushWith(Flux.just(Flux.just(buffer)));
        }
    }

    private static class HealthBoxAuthenticationEntryPoint implements
            ServerAuthenticationEntryPoint {
        @Override
        public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
            ResultEntity<Object> result = ResultEntity
                    .unauthorized()
                    .msg("没有授权")
                    .build();
            ServerHttpResponse response = exchange.getResponse();
            String json = JSON.toJSONString(result);
            DataBuffer buffer = ServerResponseUtil.writeJson2Buffer(response, json);
            ServerResponseUtil.setJsonHeaders(response);
            return response.writeAndFlushWith(Flux.just(Flux.just(buffer)));
        }
    }
}
